@using Radzen
@using System.Linq.Dynamic.Core;
@using Microsoft.JSInterop
@inherits RadzenComponent

@typeparam TItem
@implements IScheduler

@if (Visible)
{
<CascadingValue Value="@this">
    @ChildContent
</CascadingValue>
<div @ref=Element  style=@Style @attributes=@Attributes class=@GetCssClass() id=@GetId()>
    <div class="rz-scheduler-nav">
        <div class="rz-scheduler-nav-prev-next">
            <button class="rz-button rz-prev" @onclick=@OnPrev><RadzenIcon Icon="chevron_left" /></button>
            <button class="rz-button rz-next" @onclick=@OnNext><RadzenIcon Icon="chevron_right" /></button>
            <button class="rz-button rz-today" @onclick=@OnToday>@TodayText</button>
        </div>
        <div class="rz-scheduler-nav-title">@SelectedView?.Title</div>
        <div class="rz-scheduler-nav-views">
        @foreach (var view in Views)
        {
            <button class="@($"rz-button{(IsSelected(view)? " rz-state-active" : "")}")" @onclick=@(args => OnChangeView(view))>@view.Text</button>
        }
        </div>
    </div>
    @SelectedView?.Render()
</div>
}
@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public RenderFragment<TItem> Template { get; set; }

    [Parameter]
    public IEnumerable<TItem> Data { get; set; }

    [Parameter]
    public string StartProperty { get; set; }

    [Parameter]
    public string EndProperty { get; set; }

    private int selectedIndex { get; set; }
    [Parameter]
    public int SelectedIndex { get; set; }

    [Parameter]
    public string TodayText { get; set; } = "Today";

    [Parameter]
    public DateTime Date { get; set; } = DateTime.Today;

    public DateTime CurrentDate { get; set; }

    [Parameter]
    public string TextProperty { get; set; }

    [Parameter]
    public EventCallback<SchedulerSlotSelectEventArgs> SlotSelect { get; set; }

    [Parameter]
    public EventCallback<SchedulerAppointmentSelectEventArgs<TItem>> AppointmentSelect { get; set; }

    [Parameter]
    public Action<SchedulerAppointmentRenderEventArgs<TItem>> AppointmentRender { get; set; }

    [Parameter]
    public EventCallback<SchedulerLoadDataEventArgs> LoadData { get; set; }

    IList<ISchedulerView> Views { get; set; } = new List<ISchedulerView>();

    ISchedulerView SelectedView
    {
        get
        {
            return Views.ElementAtOrDefault(selectedIndex);
        }
    }

    public IDictionary<string, object> GetAppointmentAttributes(AppointmentData item)
    {
        var args = new SchedulerAppointmentRenderEventArgs<TItem> { Data = (TItem)item.Data, Start = item.Start, End = item.End };

        if (AppointmentRender != null)
        {
            AppointmentRender(args);
        }

        return args.Attributes;
    }

    public RenderFragment RenderAppointment(AppointmentData item)
    {
        if (Template != null)
        {
            TItem context = (TItem)item.Data;
            return Template(context);
        }

        return builder => builder.AddContent(0, item.Text);
    }

    public async Task SelectSlot(DateTime start, DateTime end)
    {
        await SlotSelect.InvokeAsync(new SchedulerSlotSelectEventArgs { Start = start, End = end });
    }

    public async Task SelectAppointment(AppointmentData data)
    {
        await AppointmentSelect.InvokeAsync(new SchedulerAppointmentSelectEventArgs<TItem> { Start = data.Start, End = data.End, Data = (TItem)data.Data });
    }

    public async Task AddView(ISchedulerView view)
    {
        if (!Views.Contains(view))
        {
            Views.Add(view);

            if (SelectedView == view)
            {
                await InvokeLoadData();
            }

            StateHasChanged();
        }
    }

    public async Task Reload()
    {
        appointments = null;

        await InvokeLoadData();

        StateHasChanged();
    }

    public bool IsSelected(ISchedulerView view)
    {
        return selectedIndex == Views.IndexOf(view);
    }

    async Task OnChangeView(ISchedulerView view)
    {
        selectedIndex = Views.IndexOf(view);

        await InvokeLoadData();
    }

    async Task OnPrev()
    {
        CurrentDate = SelectedView.Prev();

        await InvokeLoadData();
    }

    async Task OnToday()
    {
        CurrentDate = DateTime.Now.Date;

        await InvokeLoadData();
    }

    async Task OnNext()
    {
        CurrentDate = SelectedView.Next();

        await InvokeLoadData();
    }

    public void RemoveView(ISchedulerView view)
    {
        Views.Remove(view);
    }

    protected override void OnInitialized()
    {
        CurrentDate = Date;
        selectedIndex = SelectedIndex;

        double height = 0;

        var style = CurrentStyle;

        if (style.ContainsKey("height"))
        {
            var pixelHeight = style["height"];

            if (pixelHeight.EndsWith("px"))
            {
                height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()));
            }
        }

        if (height > 0)
        {
            heightIsSet = true;

            Height = height;
        }
    }

    IEnumerable<AppointmentData> appointments;
    DateTime rangeStart;
    DateTime rangeEnd;

    Func<TItem, DateTime> startGetter;
    Func<TItem, DateTime> endGetter;
    Func<TItem, string> textGetter;

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var needsReload = false;

        if (parameters.DidParameterChange(nameof(Date), Date))
        {
            CurrentDate = parameters.GetValueOrDefault<DateTime>(nameof(Date));
            needsReload = true;
        }

        if (parameters.DidParameterChange(nameof(SelectedIndex), SelectedIndex))
        {
            selectedIndex = parameters.GetValueOrDefault<int>(nameof(SelectedIndex));
            needsReload = true;
        }

        if (parameters.DidParameterChange(nameof(Data), Data))
        {
            appointments = null;
        }

        if (parameters.DidParameterChange(nameof(StartProperty), StartProperty))
        {
            startGetter = PropertyAccess.Getter<TItem, DateTime>(parameters.GetValueOrDefault<string>(nameof(StartProperty)));
        }

        if (parameters.DidParameterChange(nameof(EndProperty), EndProperty))
        {
            endGetter = PropertyAccess.Getter<TItem, DateTime>(parameters.GetValueOrDefault<string>(nameof(EndProperty)));
        }

        if (parameters.DidParameterChange(nameof(TextProperty), TextProperty))
        {
            textGetter = PropertyAccess.Getter<TItem, string>(parameters.GetValueOrDefault<string>(nameof(TextProperty)));
        }

        await base.SetParametersAsync(parameters);

        if (needsReload)
        {
            await InvokeLoadData();
        }
    }

    private async Task InvokeLoadData()
    {
        if (SelectedView != null)
        {
            await LoadData.InvokeAsync(new SchedulerLoadDataEventArgs { Start = SelectedView.StartDate, End = SelectedView.EndDate });
        }
    }

    public bool IsAppointmentInRange(AppointmentData item, DateTime start, DateTime end)
    {
        if (item.Start == item.End && item.Start >= start && item.End < end)
        {
            return true;
        }

        return item.End > start && item.Start < end;
    }

    public IEnumerable<AppointmentData> GetAppointmentsInRange(DateTime start, DateTime end)
    {
        if (Data == null)
        {
            return Array.Empty<AppointmentData>();
        }

        if (start == rangeStart && end == rangeEnd && appointments != null)
        {
            return appointments;
        }

        rangeStart = start;
        rangeEnd = end;

        var predicate = $"{EndProperty} >= @0 && {StartProperty} < @1";

        appointments = Data.AsQueryable()
                           .Where(predicate, start, end)
                           .ToList()
                           .Select(item => new AppointmentData { Start = startGetter(item), End = endGetter(item), Text = textGetter(item), Data = item });

        return appointments;
    }

    class Rect
    {
        public double Width { get; set; }
        public double Height { get; set; }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.createScheduler", Element, Reference);

            if (!heightIsSet)
            {
                heightIsSet = true;
                Resize(rect.Width, rect.Height);
            }
        }
    }

    [JSInvokable]
    public void Resize(double width, double height)
    {
        var stateHasChanged = false;

        if (height != Height)
        {
            Height = height;
            stateHasChanged = true;
        }

        if (stateHasChanged)
        {
            StateHasChanged();
        }
    }

    public override void Dispose()
    {
        base.Dispose();

        if (IsJSRuntimeAvailable)
        {
            JSRuntime.InvokeVoidAsync("Radzen.destroyScheduler", Element);
        }
    }

    private bool heightIsSet = false;
    private double Height { get; set; } = 400; // Default height set from theme.

    double IScheduler.Height
    {
        get
        {
            return Height;
        }
    }
    protected override string GetComponentCssClass()
    {
        return $"rz-scheduler";
    }
}